﻿
namespace Anycmd.Host.AC.MemorySets.Impl
{
    using Anycmd.AC;
    using Bus;
    using Exceptions;
    using Extensions;
    using Host;
    using Host.AC.Messages;
    using Host.Impl;
    using Repositories;
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Linq;
    using InOuts;

    public sealed class DSDSetSet : IDSDSetSet
    {
        public static readonly IDSDSetSet Empty = new DSDSetSet(EmptyACDomain.SingleInstance);

        private readonly Dictionary<Guid, DSDSetState> _dsdSetDic = new Dictionary<Guid, DSDSetState>();
        private readonly Dictionary<DSDSetState, List<DSDRoleState>> _dsdRoleBySet = new Dictionary<DSDSetState, List<DSDRoleState>>();
        private readonly Dictionary<Guid, DSDRoleState> _dsdRoleByID = new Dictionary<Guid, DSDRoleState>();
        private bool _initialized = false;

        private readonly Guid _id = Guid.NewGuid();
        private readonly IACDomain host;
        public Guid Id
        {
            get { return _id; }
        }

        public DSDSetSet(IACDomain host)
        {
            if (host == null)
            {
                throw new ArgumentNullException("host");
            }
            this.host = host;
            new MessageHandler(this).Register();
        }

        public bool TryGetDSDSet(Guid dsdSetID, out DSDSetState dsdSet)
        {
            if (!_initialized)
            {
                Init();
            }
            return _dsdSetDic.TryGetValue(dsdSetID, out dsdSet);
        }

        public IReadOnlyCollection<DSDRoleState> GetDSDRoles(DSDSetState dsdSet)
        {
            if (!_initialized)
            {
                Init();
            }
            if (dsdSet == null)
            {
                throw new ArgumentNullException("dsdSet");
            }
            if (!_dsdRoleBySet.ContainsKey(dsdSet))
            {
                return new List<DSDRoleState>();
            }
            return _dsdRoleBySet[dsdSet];
        }

        public IReadOnlyCollection<DSDRoleState> GetDSDRoles()
        {
            if (!_initialized)
            {
                Init();
            }
            var result = new List<DSDRoleState>();
            foreach (var item in _dsdRoleByID)
            {
                result.Add(item.Value);
            }

            return result;
        }

        public bool CheckRoles(IEnumerable<RoleState> roles, out string msg)
        {
            if (!_initialized)
            {
                Init();
            }
            if (roles == null)
            {
                new ArgumentNullException("roles");
            }
            foreach (var dsdSet in _dsdSetDic.Values)
            {
                var dsdRoles = _dsdRoleBySet[dsdSet];
                var dsdCard = dsdSet.DSDCard;
                if (roles.Count(a => dsdRoles.Any(b => b.RoleID == a.Id)) > dsdCard)
                {
                    msg = "违反了" + dsdSet.Name + "约束";
                    return false;
                }
            }
            msg = string.Empty;
            return true;
        }

        public IEnumerator<DSDSetState> GetEnumerator()
        {
            if (!_initialized)
            {
                Init();
            }
            return _dsdSetDic.Values.GetEnumerator();
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            if (!_initialized)
            {
                Init();
            }
            return _dsdSetDic.Values.GetEnumerator();
        }

        private void Init()
        {
            if (!_initialized)
            {
                lock (this)
                {
                    if (!_initialized)
                    {
                        _dsdSetDic.Clear();
                        _dsdRoleBySet.Clear();
                        _dsdRoleByID.Clear();
                        var stateReder = host.GetRequiredService<IOriginalHostStateReader>();
                        var dsdSets = stateReder.GetAllDSDSets();
                        foreach (var dsdSet in dsdSets)
                        {
                            if (!(dsdSet is DSDSetBase))
                            {
                                throw new CoreException(dsdSet.GetType().Name + "必须继承" + typeof(DSDSetBase).Name);
                            }
                            if (!_dsdSetDic.ContainsKey(dsdSet.Id))
                            {
                                _dsdSetDic.Add(dsdSet.Id, DSDSetState.Create(dsdSet));
                            }
                        }
                        var dsdRoles = stateReder.GetAllDSDRoles();
                        foreach (var dsdRole in dsdRoles)
                        {
                            if (!(dsdRole is DSDRoleBase))
                            {
                                throw new CoreException(dsdRole.GetType().Name + "必须继承" + typeof(DSDRoleBase).Name);
                            }
                            DSDSetState dsdSetState;
                            if (_dsdSetDic.TryGetValue(dsdRole.DSDSetID, out dsdSetState))
                            {
                                var state = DSDRoleState.Create(dsdRole);
                                if (!_dsdRoleByID.ContainsKey(dsdRole.Id))
                                {
                                    _dsdRoleByID.Add(dsdRole.Id, state);
                                }
                                if (!_dsdRoleBySet.ContainsKey(dsdSetState))
                                {
                                    _dsdRoleBySet.Add(dsdSetState, new List<DSDRoleState>());
                                }
                                _dsdRoleBySet[dsdSetState].Add(state);
                            }
                            else
                            {
                                // TODO:删除非法的记录
                            }
                        }
                        _initialized = true;
                    }
                }
            }
        }

        #region MessageHandler
        private class MessageHandler :
            IHandler<AddDSDSetCommand>,
            IHandler<DSDSetAddedEvent>,
            IHandler<DSDSetUpdatedEvent>,
            IHandler<UpdateDSDSetCommand>,
            IHandler<RemoveDSDSetCommand>,
            IHandler<DSDSetRemovedEvent>,
            IHandler<AddDSDRoleCommand>,
            IHandler<RemoveDSDRoleCommand>,
            IHandler<DSDRoleAddedEvent>,
            IHandler<DSDRoleRemovedEvent>
        {
            private readonly DSDSetSet set;

            public MessageHandler(DSDSetSet set)
            {
                this.set = set;
            }

            public void Register()
            {
                var messageDispatcher = set.host.MessageDispatcher;
                if (messageDispatcher == null)
                {
                    throw new ArgumentNullException("messageDispatcher has not be set of host:{0}".Fmt(set.host.Name));
                }
                messageDispatcher.Register((IHandler<AddDSDSetCommand>)this);
                messageDispatcher.Register((IHandler<DSDSetAddedEvent>)this);
                messageDispatcher.Register((IHandler<UpdateDSDSetCommand>)this);
                messageDispatcher.Register((IHandler<DSDSetUpdatedEvent>)this);
                messageDispatcher.Register((IHandler<RemoveDSDSetCommand>)this);
                messageDispatcher.Register((IHandler<DSDSetRemovedEvent>)this);
                messageDispatcher.Register((IHandler<AddDSDRoleCommand>)this);
                messageDispatcher.Register((IHandler<RemoveDSDRoleCommand>)this);
                messageDispatcher.Register((IHandler<DSDRoleAddedEvent>)this);
                messageDispatcher.Register((IHandler<DSDRoleRemovedEvent>)this);
            }

            public void Handle(AddDSDSetCommand message)
            {
                this.Handle(message.Input, true);
            }

            public void Handle(DSDSetAddedEvent message)
            {
                if (message.GetType() == typeof(PrivateDSDSetAddedEvent))
                {
                    return;
                }
                this.Handle(message.Output, false);
            }

            private void Handle(IDSDSetCreateIO input, bool isCommand)
            {
                var host = set.host;
                var _dsdSetDic = set._dsdSetDic;
                var dsdSetRepository = host.GetRequiredService<IRepository<DSDSet>>();
                if (!input.Id.HasValue)
                {
                    throw new ValidationException("标识是必须的");
                }
                if (host.DSDSetSet.Any(a => a.Name.Equals(input.Name, StringComparison.OrdinalIgnoreCase)))
                {
                    throw new ValidationException("重复的动态责任分离角色集名称");
                }

                var entity = DSDSet.Create(input);

                lock (this)
                {
                    DSDSetState dsdSet;
                    if (host.DSDSetSet.TryGetDSDSet(entity.Id, out dsdSet))
                    {
                        throw new CoreException("意外的重复标识");
                    }
                    if (!_dsdSetDic.ContainsKey(entity.Id))
                    {
                        _dsdSetDic.Add(entity.Id, DSDSetState.Create(entity));
                    }
                    if (isCommand)
                    {
                        try
                        {
                            dsdSetRepository.Add(entity);
                            dsdSetRepository.Context.Commit();
                        }
                        catch
                        {
                            if (_dsdSetDic.ContainsKey(entity.Id))
                            {
                                _dsdSetDic.Remove(entity.Id);
                            }
                            dsdSetRepository.Context.Rollback();
                            throw;
                        }
                    }
                }
                if (isCommand)
                {
                    host.MessageDispatcher.DispatchMessage(new PrivateDSDSetAddedEvent(entity, input));
                }
            }

            private class PrivateDSDSetAddedEvent : DSDSetAddedEvent
            {
                public PrivateDSDSetAddedEvent(DSDSetBase source, IDSDSetCreateIO input)
                    : base(source, input)
                {
                }
            }

            public void Handle(UpdateDSDSetCommand message)
            {
                this.Handle(message.Output, true);
            }

            public void Handle(DSDSetUpdatedEvent message)
            {
                if (message.GetType() == typeof(PrivateDSDSetUpdatedEvent))
                {
                    return;
                }
                this.Handle(message.Output, false);
            }

            private void Handle(IDSDSetUpdateIO input, bool isCommand)
            {
                var host = set.host;
                var _dsdSetDic = set._dsdSetDic;
                var dsdSetRepository = host.GetRequiredService<IRepository<DSDSet>>();
                DSDSetState bkState;
                if (!host.DSDSetSet.TryGetDSDSet(input.Id, out bkState))
                {
                    throw new NotExistException();
                }
                DSDSet entity;
                bool stateChanged = false;
                lock (bkState)
                {
                    DSDSetState oldState;
                    if (!host.DSDSetSet.TryGetDSDSet(input.Id, out oldState))
                    {
                        throw new NotExistException();
                    }
                    if (host.DSDSetSet.Any(a => a.Name.Equals(input.Name, StringComparison.OrdinalIgnoreCase) && a.Id != input.Id))
                    {
                        throw new ValidationException("重复的静态责任分离角色组名");
                    }
                    entity = dsdSetRepository.GetByKey(input.Id);
                    if (entity == null)
                    {
                        throw new NotExistException();
                    }

                    entity.Update(input);

                    var newState = DSDSetState.Create(entity);
                    stateChanged = newState != bkState;
                    if (stateChanged)
                    {
                        Update(newState);
                    }
                    if (isCommand)
                    {
                        try
                        {
                            dsdSetRepository.Update(entity);
                            dsdSetRepository.Context.Commit();
                        }
                        catch
                        {
                            if (stateChanged)
                            {
                                Update(bkState);
                            }
                            dsdSetRepository.Context.Rollback();
                            throw;
                        }
                    }
                }
                if (isCommand && stateChanged)
                {
                    host.MessageDispatcher.DispatchMessage(new PrivateDSDSetUpdatedEvent(entity, input));
                }
            }

            private void Update(DSDSetState state)
            {
                var host = set.host;
                var _dsdSetDic = set._dsdSetDic;
                var _dsdRoleBySet = set._dsdRoleBySet;
                var oldState = _dsdSetDic[state.Id];
                _dsdSetDic[state.Id] = state;
                if (_dsdRoleBySet.ContainsKey(oldState) && !_dsdRoleBySet.ContainsKey(state))
                {
                    _dsdRoleBySet.Add(state, _dsdRoleBySet[oldState]);
                    _dsdRoleBySet.Remove(oldState);
                }
            }

            private class PrivateDSDSetUpdatedEvent : DSDSetUpdatedEvent
            {
                public PrivateDSDSetUpdatedEvent(DSDSetBase source, IDSDSetUpdateIO input)
                    : base(source, input)
                {

                }
            }

            public void Handle(RemoveDSDSetCommand message)
            {
                this.Handle(message.EntityID, true);
            }

            public void Handle(DSDSetRemovedEvent message)
            {
                if (message.GetType() == typeof(PrivateDSDSetRemovedEvent))
                {
                    return;
                }
                this.Handle(message.Source.Id, false);
            }

            private void Handle(Guid dsdSetID, bool isCommand)
            {
                var host = set.host;
                var _dsdSetDic = set._dsdSetDic;
                var dsdSetRepository = host.GetRequiredService<IRepository<DSDSet>>();
                DSDSetState bkState;
                if (!host.DSDSetSet.TryGetDSDSet(dsdSetID, out bkState))
                {
                    return;
                }
                DSDSet entity;
                lock (bkState)
                {
                    DSDSetState state;
                    if (!host.DSDSetSet.TryGetDSDSet(dsdSetID, out state))
                    {
                        return;
                    }
                    entity = dsdSetRepository.GetByKey(dsdSetID);
                    if (entity == null)
                    {
                        return;
                    }
                    if (_dsdSetDic.ContainsKey(bkState.Id))
                    {
                        _dsdSetDic.Remove(bkState.Id);
                    }
                    if (isCommand)
                    {
                        try
                        {
                            dsdSetRepository.Remove(entity);
                            dsdSetRepository.Context.Commit();
                        }
                        catch
                        {
                            if (!_dsdSetDic.ContainsKey(entity.Id))
                            {
                                _dsdSetDic.Add(bkState.Id, bkState);
                            }
                            dsdSetRepository.Context.Rollback();
                            throw;
                        }
                    }
                }
                if (isCommand)
                {
                    host.MessageDispatcher.DispatchMessage(new PrivateDSDSetRemovedEvent(entity));
                }
            }

            private class PrivateDSDSetRemovedEvent : DSDSetRemovedEvent
            {
                public PrivateDSDSetRemovedEvent(DSDSetBase source)
                    : base(source)
                {
                }
            }

            public void Handle(AddDSDRoleCommand message)
            {
                this.Handle(message.Input, true);
            }

            public void Handle(DSDRoleAddedEvent message)
            {
                if (message.GetType() == typeof(PrivateDSDRoleAddedEvent))
                {
                    return;
                }
                this.Handle(message.Output, false);
            }

            private void Handle(IDSDRoleCreateIO input, bool isCommand)
            {
                var host = set.host;
                var _dsdRoleBySet = set._dsdRoleBySet;
                var _dsdRoleByID = set._dsdRoleByID;
                var dsdRoleRepository = host.GetRequiredService<IRepository<DSDRole>>();
                if (!input.Id.HasValue)
                {
                    throw new ValidationException("标识是必须的");
                }
                if (_dsdRoleByID.Any(a => a.Key == input.Id.Value || (a.Value.RoleID == input.RoleID && a.Value.DSDSetID == input.DSDSetID)))
                {
                    throw new ValidationException("重复的记录");
                }
                DSDSetState dsdSet;
                if (!host.DSDSetSet.TryGetDSDSet(input.DSDSetID, out dsdSet))
                {
                    throw new ValidationException("意外的动态责任分离角色集标识" + input.DSDSetID);
                }

                var entity = DSDRole.Create(input);

                lock (this)
                {
                    if (_dsdRoleByID.Any(a => a.Key == input.Id.Value || (a.Value.RoleID == input.RoleID && a.Value.DSDSetID == input.DSDSetID)))
                    {
                        throw new ValidationException("重复的记录");
                    }
                    if (!host.DSDSetSet.TryGetDSDSet(input.DSDSetID, out dsdSet))
                    {
                        throw new ValidationException("意外的动态责任分离角色集标识" + input.DSDSetID);
                    }
                    var state = DSDRoleState.Create(entity);
                    if (!_dsdRoleByID.ContainsKey(entity.Id))
                    {
                        _dsdRoleByID.Add(entity.Id, state);
                    }
                    if (!_dsdRoleBySet.ContainsKey(dsdSet))
                    {
                        _dsdRoleBySet.Add(dsdSet, new List<DSDRoleState>());
                    }
                    _dsdRoleBySet[dsdSet].Add(state);
                    if (isCommand)
                    {
                        try
                        {
                            dsdRoleRepository.Add(entity);
                            dsdRoleRepository.Context.Commit();
                        }
                        catch
                        {
                            if (_dsdRoleByID.ContainsKey(entity.Id))
                            {
                                _dsdRoleByID.Remove(entity.Id);
                            }
                            _dsdRoleBySet[dsdSet].Remove(state);
                            dsdRoleRepository.Context.Rollback();
                            throw;
                        }
                    }
                }
                if (isCommand)
                {
                    host.MessageDispatcher.DispatchMessage(new PrivateDSDRoleAddedEvent(entity, input));
                }
            }

            private class PrivateDSDRoleAddedEvent : DSDRoleAddedEvent
            {
                public PrivateDSDRoleAddedEvent(DSDRoleBase source, IDSDRoleCreateIO input)
                    : base(source, input)
                {

                }
            }

            public void Handle(RemoveDSDRoleCommand message)
            {
                this.HandleDSDRole(message.EntityID, true);
            }

            public void Handle(DSDRoleRemovedEvent message)
            {
                if (message.GetType() == typeof(PrivateDSDRoleRemovedEvent))
                {
                    return;
                }
                this.HandleDSDRole(message.Source.Id, false);
            }

            private void HandleDSDRole(Guid dsdRoleID, bool isCommand)
            {
                var host = set.host;
                var _dsdSetDic = set._dsdSetDic;
                var _dsdRoleBySet = set._dsdRoleBySet;
                var _dsdRoleByID = set._dsdRoleByID;
                var dsdRoleRepository = host.GetRequiredService<IRepository<DSDRole>>();
                DSDRoleState bkState;
                if (!_dsdRoleByID.TryGetValue(dsdRoleID, out bkState))
                {
                    return;
                }
                DSDRole entity;
                lock (bkState)
                {
                    DSDRoleState state;
                    if (!_dsdRoleByID.TryGetValue(dsdRoleID, out state))
                    {
                        return;
                    }
                    entity = dsdRoleRepository.GetByKey(dsdRoleID);
                    if (entity == null)
                    {
                        return;
                    }
                    if (_dsdRoleByID.ContainsKey(bkState.Id))
                    {
                        _dsdRoleByID.Remove(bkState.Id);
                    }
                    DSDSetState dsdSet;
                    if (_dsdSetDic.TryGetValue(entity.DSDSetID, out dsdSet))
                    {
                        _dsdRoleBySet[dsdSet].Remove(bkState);
                    }
                    if (isCommand)
                    {
                        try
                        {
                            dsdRoleRepository.Remove(entity);
                            dsdRoleRepository.Context.Commit();
                        }
                        catch
                        {
                            if (!_dsdRoleByID.ContainsKey(entity.Id))
                            {
                                _dsdRoleByID.Add(bkState.Id, bkState);
                            }
                            _dsdRoleBySet[dsdSet].Add(bkState);
                            dsdRoleRepository.Context.Rollback();
                            throw;
                        }
                    }
                }
                if (isCommand)
                {
                    host.MessageDispatcher.DispatchMessage(new PrivateDSDRoleRemovedEvent(entity));
                }
            }

            private class PrivateDSDRoleRemovedEvent : DSDRoleRemovedEvent
            {
                public PrivateDSDRoleRemovedEvent(DSDRoleBase source)
                    : base(source)
                {
                }
            }
        }
        #endregion
    }
}
